#include "python.h"

Python *python; //wrapper for python API
const char* event_type_list[] = {"INPUT_KEY", "INPUT_CLICK", "INPUT_WHEEL", "ACTION_UPDATE", "ACTION_ADD", "ACTION_DELETE", "ACTION_CHAT", "ACTION_SYSTEM", NULL};

typedef struct {
	PyObject_HEAD
	Screen_console *screen;
/*	PyObject *first; // first name 
	PyObject *last;  // last name
	int number;*/
} Data_screen_console;

static PyObject *new_screen_console(PyTypeObject *type, PyObject *args, PyObject *kwds){
	Data_screen_console *self;
	self = (Data_screen_console *)type->tp_alloc(type, 0);
	if (self != NULL) {
		/*		self->first = PyString_FromString("");
		if (self->first == NULL){
 Py_DECREF(self);
 return NULL;
	}
	self->last = PyString_FromString("");
	if (self->last == NULL){
 Py_DECREF(self);
 return NULL;
	}
	self->number = 0;*/
	}
	return (PyObject *)self;
}

static int init_screen_console (Data_screen_console *self, PyObject *args, PyObject *kwds){
	self->screen=new Screen_console;
	if(!python->interface->add_screen(self->screen)){
		//TODO: Throw error/exception!
		return -1;
	}
	/*PyObject *first=NULL, *last=NULL, *tmp;
	static char *kwlist[] = {"first", "last", "number", NULL};
	if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,	&first, &last, &self->number))
	return -1;
	if (first) {
 tmp = self->first;
 Py_INCREF(first);
 self->first = first;
 Py_XDECREF(tmp);
}
if (last) {
 tmp = self->last;
 Py_INCREF(last);
 self->last = last;
 Py_XDECREF(tmp);
}*/
	return 0;
}

//getMode(): returns a string describing current screen state (game, chat, map,...)
static PyObject *screen_console_get_mode(PyObject *self, PyObject *args, PyObject *keywds){
	//tmp
	return Py_BuildValue("s", "game");
}

//msgbox(text, buttons...): show a message box and return its result (python will wait)
static PyObject *screen_console_msgbox(Data_screen_console *self, PyObject* args, PyObject *keywds){
	char *text;
	int *x, *y;
	static char *kwlist[] = {(char*)"text", (char*)"x", "y", NULL};
	if (!PyArg_ParseTupleAndKeywords(args, keywds, "s|ii", kwlist, &text, &x, &y)){
		Py_RETURN_NONE;
	}
	bool return_value = self->screen->draw_msgbox(string(text));
	python->interface->blit(); //temporary
	return Py_BuildValue("i", (int)return_value);
}

//addWidget(type, x, y, [additional parameters]): Add a widget using Interface
static PyObject *screen_console_add_widget(PyObject *self, PyObject* args, PyObject *keywds){
	Py_RETURN_NONE;
}

//getWidget(...): returns a new widget object, representing a in-game Widget
static PyObject *screen_console_get_widget(PyObject *self, PyObject* args, PyObject *keywds){
	Py_RETURN_NONE;
}

//deleteWidget(...
static PyObject *screen_console_delete_widget(PyObject *self, PyObject* args, PyObject *keywds){
	Py_RETURN_NONE;
}

//menubox(name, items...): show a dropdown menu and return id of the chosen item or -1 for exit
static PyObject *screen_console_menubox(PyObject *self, PyObject* args, PyObject *keywds){
	Py_RETURN_NONE;
}

//updateStatusbar(text, isHidden): alter the statusbar present in most screen modes
static PyObject *screen_console_update_statusbar(PyObject *self, PyObject* args, PyObject *keywds){
	Py_RETURN_NONE;
}

static PyMethodDef methods_screen_console[] = {
	{"msgbox", (PyCFunction)screen_console_msgbox, METH_KEYWORDS, "Create an automated message box"},
	{"getMode", (PyCFunction)screen_console_get_mode, METH_NOARGS, "Get screen mode as string"},
	{"addWidget", (PyCFunction)screen_console_add_widget, METH_KEYWORDS, ""},
	{"getWidget", (PyCFunction)screen_console_get_widget, METH_KEYWORDS, ""},
	{"deleteWidget", (PyCFunction)screen_console_delete_widget, METH_KEYWORDS, ""},
	{"menubox", (PyCFunction)screen_console_menubox, METH_KEYWORDS, ""},
	{"updateStatusbar", (PyCFunction)screen_console_update_statusbar, METH_KEYWORDS, ""},
	{NULL}  /* Sentinel */
};

static PyMemberDef members_screen_console[] = {
	/*{"first", T_OBJECT_EX, offsetof(Data_screen_console, first), 0,	"first name"},
	{"last", T_OBJECT_EX, offsetof(Data_screen_console, last), 0,	"last name"},
	{"number", T_INT, offsetof(Data_screen_console, number), 0,	"just a number"},*/
	{NULL}  /* Sentinel */
};

static void dealloc_screen_console(Data_screen_console* self){
	/*Py_XDECREF(self->first);
	Py_XDECREF(self->last);*/
	self->ob_type->tp_free((PyObject*)self);
}

static PyTypeObject type_screen_console = {
	PyObject_HEAD_INIT(NULL)
	0,
	"pe.screenConsole",             /*tp_name*/
	sizeof(Data_screen_console),             /*tp_basicsize*/
	0,
	(destructor)dealloc_screen_console, /*tp_dealloc*/
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
	"Full-window console input and output",           /* tp_doc */
	0,0,0,0,0,0,
	methods_screen_console,             /* tp_methods */
	members_screen_console,             /* tp_members */
	0,0,0,0,0,0,
	(initproc)init_screen_console,      /* tp_init */
	0,
	new_screen_console,                 /* tp_new */
};

typedef struct {
	PyObject_HEAD
	PyObject *type; //unit, building, frame, menu, menu_item, msgbox, statusbar
	PyObject *x, *y; //type: unit, building, frame, menu, menu_item, msgbox
	PyObject *unit_id, *unit_type, *owner; //type: unit (owner is used also by buildings)
	PyObject *building_id, *building_type; //type: building
	PyObject *items; //type: menu
	PyObject *text; //type: menu_item, msgbox, statusbar
} Data_screen_console_widget;

static PyObject *new_screen_console_widget(PyTypeObject *type, PyObject *args, PyObject *kwds){
	Data_screen_console_widget *self;
	self = (Data_screen_console_widget *)type->tp_alloc(type, 0);
	if (self != NULL) {
	}
	return (PyObject *)self;
}

static int init_screen_console_widget (Data_screen_console_widget *self, PyObject *args, PyObject *kwds){return 0;}

static PyMethodDef methods_screen_console_widget[] = {{NULL}};

static PyMemberDef members_screen_console_widget[] = {
	{NULL}  /* Sentinel */
};

static void dealloc_screen_console_widget(Data_screen_console_widget* self){
	self->ob_type->tp_free((PyObject*)self);
}

static PyTypeObject type_screen_console_widget = {
	PyObject_HEAD_INIT(NULL)
	0,                         /*ob_size*/
	"pe.screen_console_widget",             /*tp_name*/
	sizeof(Data_screen_console_widget),             /*tp_basicsize*/
	0,                         /*tp_itemsize*/
	(destructor)dealloc_screen_console_widget, /*tp_dealloc*/
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
	"Widget on the console screen",           /* tp_doc */
	0,0,0,0,0,0,
	methods_screen_console_widget,             /* tp_methods */
	members_screen_console_widget,             /* tp_members */
	0,0,0,0,0,0,
	(initproc)init_screen_console_widget,      /* tp_init */
	0,                         /* tp_alloc */
	PyType_GenericNew, //MAY CAUSE ERROR ON SOME COMPILERS                 /* tp_new */
};

typedef struct {
	PyObject_HEAD
	PyObject *is_shift, *is_ctrl, *is_alt, //generic
	*type, //input_key, input_click, input_wheel, action_update, action_add, action_delete, action_chat, action_system, action_effect
	*target_type,
	*unit_id;
} Data_event;

static PyObject *new_event(PyTypeObject *type, PyObject *args, PyObject *kwds){
	Data_event *self;
	self = (Data_event *)type->tp_alloc(type, 0);
	if (self != NULL) {
		Py_INCREF(Py_False);
		self->is_shift=Py_False;
		Py_INCREF(Py_False);
		self->is_ctrl=Py_False;
		Py_INCREF(Py_False);
		self->is_alt=Py_False;
		self->unit_id=Py_BuildValue("i", 0);
		Py_INCREF(Py_None);
		self->target_type=Py_None;
	}
	return (PyObject *)self;
}

static int init_event (Data_event *self, PyObject *args, PyObject *kwds){return 0;}

static PyMethodDef methods_event[] = {{NULL}};

static PyMemberDef members_event[] = {
	{(char*)"isShift", T_OBJECT_EX, offsetof(Data_event, is_shift), 0, (char*)"Shift flag"},
	{(char*)"isCtrl", T_OBJECT_EX, offsetof(Data_event, is_ctrl), 0, (char*)"Ctrl flag"},
	{(char*)"isAlt", T_OBJECT_EX, offsetof(Data_event, is_alt), 0, (char*)"Alt flag"},
	{(char*)"unitId", T_OBJECT_EX, offsetof(Data_event, unit_id), 0, (char*)"ID of the unit"},
	{(char*)"targetType", T_OBJECT_EX, offsetof(Data_event, target_type), 0, (char*)"Target type (unit/building)"},
	{NULL}  /* Sentinel */
};

static void dealloc_event(Data_event* self){
	Py_XDECREF(self->is_shift);
	Py_XDECREF(self->is_alt);
	Py_XDECREF(self->is_ctrl);
	Py_XDECREF(self->unit_id);
	Py_XDECREF(self->target_type);
	self->ob_type->tp_free((PyObject*)self);
}

static PyTypeObject type_event = {
	PyObject_HEAD_INIT(NULL)
	0,                         /*ob_size*/
	"pe.event",             /*tp_name*/
	sizeof(Data_event),             /*tp_basicsize*/
	0,                         /*tp_itemsize*/
	(destructor)dealloc_event, /*tp_dealloc*/
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
	"Event from the game",           /* tp_doc */
	0,0,0,0,0,0,
	methods_event,             /* tp_methods */
	members_event,             /* tp_members */
	0,0,0,0,0,0,
	(initproc)init_event,      /* tp_init */
	0,                         /* tp_alloc */
	new_event,                 /* tp_new */
};

typedef struct {
	PyObject_HEAD
} Data_game;

static PyObject *new_game(PyTypeObject *type, PyObject *args, PyObject *kwds){
	Data_game *self;
	self = (Data_game *)type->tp_alloc(type, 0);
	if (self != NULL) {
	}
	return (PyObject *)self;
}

static int init_game (Data_game *self, PyObject *args, PyObject *kwds){
	return 0;
}

static PyMethodDef methods_game[] = {
	{NULL}  /* Sentinel */
};

static PyMemberDef members_game[] = {
	{NULL}  /* Sentinel */
};

static void dealloc_game(Data_game* self){
	self->ob_type->tp_free((PyObject*)self);
}

static PyTypeObject type_game = {
	PyObject_HEAD_INIT(NULL)
	0,                         /*ob_size*/
	"pe.game",             /*tp_name*/
	sizeof(Data_game),             /*tp_basicsize*/
	0,                         /*tp_itemsize*/
	(destructor)dealloc_game, /*tp_dealloc*/
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
	"Main game object",           /* tp_doc */
	0,0,0,0,0,0,
	methods_game,             /* tp_methods */
	members_game,             /* tp_members */
	0,0,0,0,0,0,
	(initproc)init_game,      /* tp_init */
	0,                         /* tp_alloc */
	new_game,                 /* tp_new */
};

//setCallback(function, eventType, screen, filters...): set callback function for processing the given event
static PyObject *pe_set_callback(PyObject *self, PyObject* args, PyObject *keywds){
	char *event, *key;
	PyObject *function, *screen;
	static char *kwlist[] = {(char*)"function", (char*)"event", "screen", (char*)"key", NULL};
	if (!PyArg_ParseTupleAndKeywords(args, keywds, "Os|Os", kwlist, &function, &event, &screen, &key)){
		Py_INCREF(Py_None);
		return Py_None;
	}
	if (!function || !PyCallable_Check(function)){
		Py_XDECREF(function);
		printf("Failed to set callback\n");
		PyErr_SetString(PyExc_TypeError, "parameter must be callable");
		return Py_BuildValue("i", -1);
	}
	else {
		for (int i_event_type=0; event_type_list[i_event_type]; i_event_type++){
			//fprintf(stdout, "trying %i: %s vs. %s\n", i_event_type, event, event_type_list[i_event_type]);
			if (strcmp(event_type_list[i_event_type], event)==0){
				python->set_callback(i_event_type, function);
				printf("(%s) ", event_type_list[i_event_type]);
			}
		}
	}
	Py_DECREF(function);
	return Py_BuildValue("i",0);
};

//clearCallback(eventType): remove a callback definition (if exists)
static PyObject *pe_clear_callback(PyObject *self, PyObject* args, PyObject *keywds){
	return Py_None;
}

//sendCommand(command, parameters...): Send a given command to the server
static PyObject *pe_send_command(PyObject *self, PyObject* args, PyObject *keywds){
	Command *command=new Command;
	char *function, *event, *key;
	static char *kwlist[] = {(char*)"function", (char*)"event", (char*)"key", NULL};
	if (!PyArg_ParseTupleAndKeywords(args, keywds, "ss|s", kwlist, &function, &event, &key))
		return NULL;
	//command_cache.push_back(command);
	return Py_BuildValue("i",0);
};

PyMethodDef pe_methods[] = {
	{"setCallback", (PyCFunction)pe_set_callback, METH_KEYWORDS, "Set python callback function for processing given event"},
	{"clearCallback"},
	{"sendCommand", (PyCFunction)pe_send_command, METH_KEYWORDS, "Send a given command to the game server"},
	{NULL, NULL}
};

Python::Python(Interface *iinterface){
	python=this;
	interface=iinterface;
// 	Py_SetProgramName("pure_evil client");
	Py_Initialize();
	PySys_SetPath(".");
	PyImport_AddModule("pe");
	PyObject *pe = Py_InitModule("pe", pe_methods);
	if (PyType_Ready(&type_screen_console)==0){
		Py_INCREF(&type_screen_console);
		PyModule_AddObject(pe, "screenConsole", (PyObject *)&type_screen_console);
	}
	if (PyType_Ready(&type_screen_console_widget)==0){
		Py_INCREF(&type_screen_console_widget);
		PyModule_AddObject(pe, "screenConsoleWidget", (PyObject *)&type_screen_console_widget);
	}
	if (PyType_Ready(&type_event)==0){
		Py_INCREF(&type_event);
		PyModule_AddObject(pe, "event", (PyObject *)&type_event);
	}
	if (PyType_Ready(&type_game)==0){
		Py_INCREF(&type_game);
		PyModule_AddObject(pe, "game", (PyObject *)&type_game);
	}
	load_script("user_interface");
	fprintf(stderr, "Loaded settings file\n");
	//	PySys_SetPath(Py_GetPath());
};

void Python::load_script(char* file_name){
	PyObject *pModule = PyImport_Import(PyString_FromString("user_interface"));
	PyObject *pName;//, *pModule, *pFunc;
	/*PyObject *pArgs, *pValue;
	int i;*/
	pName = PyString_FromString("user_interface");
	/* Error checking of pName left out */
	//pModule = PyImport_Import(pName);
	scripts.push_back(pModule);
	Py_DECREF(pName);
	if (pModule != NULL) {
		/*pFunc = PyObject_GetAttrString(pModule, "initialize");
		if (pFunc && PyCallable_Check(pFunc)) {
			pArgs = PyTuple_New(0);
			//PyTuple_SetItem(pArgs, 0, PyInt_FromLong(8));
			pValue = PyObject_CallObject(pFunc, pArgs);
			Py_DECREF(pArgs);
			if (pValue != NULL) {
				printf("Result of call: %ld\n", PyInt_AsLong(pValue));
				Py_DECREF(pValue);
			}
			else {
				Py_DECREF(pFunc);
				Py_DECREF(pModule);
				PyErr_Print();
				fprintf(stderr,"Call failed\n");
			}
		Py_XDECREF(pFunc);*/
		Py_DECREF(pModule);
		}
		else {
			if (PyErr_Occurred())
				PyErr_Print();
		}
/*	else {
		PyErr_Print();
		fprintf(stderr, "Failed to load \"%s\"\n", file_name);
	}*/
	return;
};

Python::~Python(){
	Py_DECREF(&type_screen_console);
	Py_DECREF(&type_event);
	Py_Finalize();
};
void Python::set_callback(int event_type, PyObject* function){
	Py_XINCREF(function);
	callbacks.insert(pair <int, PyObject*> (event_type, function));
}
void Python::process_event(Event* event){
	PyObject *args=NULL, *value, *function=NULL;
	int j;
	printf("Received event of type %i: %s\n", event->type, event_type_list[event->type]);
	function=callbacks[event->type];
	if (function && PyCallable_Check(function)) {
		printf("Callback function found. Calling...\n");
		args=PyTuple_New(0);
		PyObject *py_event= PyObject_CallObject((PyObject *) &type_event, args);
		Py_DECREF(args);
		switch(event->type){
			case INP_KEY:
				break;
			case INP_CLICK:
				break;
			case INP_WHEEL:
				break;
			case ACT_UPDATE:
				break;
			case ACT_ADD:
				break;
			case ACT_DELETE:
				break;
			case ACT_CHAT:
				break;
			case ACT_SYSTEM:
				break;
			case ACT_EFFECT:
				break;
		}
		args = PyTuple_New(1);
		PyTuple_SetItem(args, 0, py_event);
		value = PyObject_CallObject(function, args);
		printf ("Succesfully called.\n");
		Py_DECREF(args);
		if (value != NULL) {
			Py_DECREF(value);
		}
		else {
			fprintf(stderr,"Callback failed\n");
			PyErr_Print();
		}
	}
	return;
};

//call the PyObject callback_function and return result of the call
PyObject* Callback::call(Event* event){
	PyObject *value, *args;
	value = PyObject_CallObject(callback_function, args);
	Py_DECREF(args);
	if (value != NULL) {
		return value;
	}
	else {
		PyErr_Print();
		fprintf(stderr, "Call failed\n");
		Py_INCREF(Py_None);
		return Py_None;
	}
}

Callback::Callback(PyObject* function){
	callback_function=function;
	Py_INCREF(callback_function);
}

Callback::~Callback(){
	Py_XDECREF(callback_function);
}
